Here are the modifications to implement your requested improvements for the Edit Mode.
Add the following styles to the <style> block to manage the layout of the palette and comment grid, ensuring they stay responsive and fully visible.
CSS
/* --- Edit Mode UI Grid Styles --- */
.edit-palette-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 5px;
width: 100%;
padding: 5px;
box-sizing: border-box;
}
.edit-comment-grid {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
gap: 10px;
padding: 10px;
box-sizing: border-box;
align-content: center;
}
.edit-palette-item {
width: 100%;
aspect-ratio: 1;
cursor: pointer;
box-sizing: border-box;
}
.edit-comment-item, .ejceespbmini-container {
flex: 0 0 40%;
max-width: 64px;
aspect-ratio: 1;
cursor: pointer;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
}
/* Mobile specific adjustments */
@media (max-width: 767px) {
.ejceescomment {
display: flex;
flex-direction: column;
justify-content: center;
}
/* Using an aspect-ratio media query to detect if .ejceescomment container would be wide/short */
@media (min-aspect-ratio: 1/1) {
.edit-comment-grid {
flex-wrap: nowrap;
}
.edit-comment-item, .ejceespbmini-container {
flex: 1 1 20%;
max-width: 48px;
}
}
}
Modify this function to utilize the new classes and remove the inline borders.
JavaScript
function getEditIconHTML(char, cssClass) {
const tileId = pieceToHrefMap[char] || 'tile0';
const tile0stroke = tileId === 'tile0' ? 'orange' : 'none';
const tile0animtion = tileId === 'tile0' ? '<animateTransform attributeName="transform" attributeType="XML" type="translate" values="24,24;24,12;24,24;24,36;24,24" dur="3.6s" repeatCount="indefinite"/>' : '';
const origTile = document.getElementById(tileId);
const defsContent = origTile ? origTile.outerHTML : '';
return `
<svg class="${cssClass}" data-char="${char}" viewBox="0 0 48 48">
<defs>${defsContent}</defs>
<use id="edit-${char}" href="#${tileId}" fill="none" stroke="${tile0stroke}" transform="translate(24,24) scale(1,1) rotate(0)">${tile0animtion}</use>
</svg>`;
}
Modify this function to implement the new grid layout, hide .ejceesstep, and display the initial long text tip.
JavaScript
function enterEditMode() {
isEditMode = true;
preEditFEN = boardToFEN();
editUndoStack = [preEditFEN];
editRedoStack = [];
isTile0Selected = true;
selectedPalettePiece = null;
editMiniBoardState = 0;
editPickedPieceId = null;
document.querySelector('.ejceesstep').style.display = 'none';
setOutputText("Edit Mode: Select a piece to place or move.", false);
if (pickedPieceId) setPickup(pickedPieceId, false);
pickedPieceId = null;
const pieces = etboard.querySelectorAll('use');
pieces.forEach(el => {
const href = el.getAttribute('href') || el.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
if (/^#(tile([2-9]|1[0-5]))$/.test(href)) {
el.removeAttribute('currentmove');
el.setAttribute('stroke', 'none');
el.removeAttribute('stroke-width');
}
});
const dot = document.getElementById('ejceesstartdot');
if (dot) {
dot.setAttribute('cx', initialDotCx);
dot.setAttribute('cy', initialDotCy);
}
const recordContainer = document.querySelector('.ejceesrecord');
const row1 = ['R', 'N', 'n', 'r', 'C', 'P', 'p', 'c', 'B', 'A', 'a', 'b'];
recordContainer.innerHTML = '<div class="edit-palette-grid">' + row1.map(c => getEditIconHTML(c, 'edit-palette-item')).join('') + '</div>';
const commentContainer = document.querySelector('.ejceescomment');
let commentHTML = '<div class="edit-comment-grid">';
commentHTML += getEditIconHTML('tile0', 'edit-comment-item');
commentHTML += `<div class="ejceespbmini-container"></div>`;
commentHTML += getEditIconHTML('K', 'edit-comment-item');
commentHTML += getEditIconHTML('k', 'edit-comment-item');
commentHTML += '</div>';
commentContainer.innerHTML = commentHTML;
const miniContainer = document.querySelector('.ejceespbmini-container');
const cloneSVG = document.querySelector('.ejceespb').cloneNode(true);
cloneSVG.setAttribute('class', 'ejceespbmini');
cloneSVG.removeAttribute('id');
cloneSVG.style.width = '100%';
cloneSVG.style.height = '100%';
miniContainer.appendChild(cloneSVG);
updateMiniBoard();
const btnDiv = document.querySelector('.ejceestextbtn');
btnDiv.innerHTML = `
<div class="ejceestextbtninner">
<div class="note-btn btn-confirm" id="edit-confirm" title="Confirm">
<svg viewBox="0 0 24 24"><path fill="#4CAF50" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
</div>
<div class="note-btn btn-cancel" id="edit-cancel" title="Cancel">
<svg viewBox="0 0 24 24"><path fill="#F44336" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</div>
</div>
`;
document.getElementById('edit-confirm').addEventListener('click', exitEditModeConfirm);
document.getElementById('edit-cancel').addEventListener('click', exitEditModeCancel);
bindEditUIEvents();
updateEditUI();
updateToolHighlights();
}
These functions have been updated to handle SVG stroke formatting, show short text prompts, and manage validation.
JavaScript
function bindEditUIEvents() {
document.querySelectorAll('.edit-palette-item').forEach(item => {
item.addEventListener('click', () => {
if (item.classList.contains('disabled')) return;
const char = item.getAttribute('data-char');
selectedPalettePiece = (selectedPalettePiece === char) ? null : char;
if(selectedPalettePiece) setOutputText("Selected: " + selectedPalettePiece, false);
updateEditUI();
});
});
document.querySelectorAll('.edit-comment-item').forEach(item => {
item.addEventListener('click', () => {
const char = item.getAttribute('data-char');
if (char === 'tile0' || char === 'tile1') {
const newChar = char === 'tile0' ? 'tile1' : 'tile0';
item.setAttribute('data-char', newChar);
const useEl = item.querySelector('use');
if (useEl) {
useEl.setAttribute('href', `#${newChar}`);
useEl.setAttributeNS('http://www.w3.org/1999/xlink', 'href', `#${newChar}`);
if (newChar === 'tile0') {
useEl.innerHTML = '<animateTransform attributeName="transform" attributeType="XML" type="translate" values="24,24;24,12;24,24;24,36;24,24" dur="3.6s" repeatCount="indefinite"/>';
} else {
useEl.innerHTML = '<animateTransform attributeName="transform" attributeType="XML" type="rotate" values="0;180" calcMode="discrete" dur="3.6s" repeatCount="indefinite" additive="sum"/>';
}
}
isTile0Selected = !isTile0Selected;
setOutputText("Option changed.", true);
} else if (char === 'K' || char === 'k') {
currentTurn = (char === 'K') ? 'w' : 'b';
saveEditState();
setOutputText("Turn changed.", true);
}
updateEditUI();
});
});
document.querySelector('.ejceespbmini-container').addEventListener('click', () => {
editMiniBoardState = 1 - editMiniBoardState;
const newFen = editMiniBoardState === 0 ? INITIAL_FEN : '4k4/9/9/9/9/9/9/9/9/4K4 w';
const savedTurn = currentTurn;
loadFEN(newFen);
currentTurn = savedTurn;
updateMiniBoard();
saveEditState();
updateEditUI();
setOutputText("Mini board layout changed.", true);
});
}
function updateEditUI() {
const counts = {};
for (const id of tileMap.values()) counts[id[0]] = (counts[id[0]] || 0) + 1;
document.querySelectorAll('.edit-palette-item').forEach(item => {
const char = item.getAttribute('data-char');
const max = (char === 'p' || char === 'P') ? 5 : 2;
if ((counts[char] || 0) >= max) {
item.style.opacity = '0.36';
item.classList.add('disabled');
if (selectedPalettePiece === char) selectedPalettePiece = null;
} else {
item.style.opacity = '1';
item.classList.remove('disabled');
}
const useEl = item.querySelector('use');
if (useEl) {
if (selectedPalettePiece === char) {
useEl.setAttribute('stroke', 'white');
useEl.setAttribute('stroke-width', '2');
} else {
useEl.setAttribute('stroke', 'none');
}
}
});
document.querySelectorAll('.edit-comment-item').forEach(item => {
const char = item.getAttribute('data-char');
const useEl = item.querySelector('use');
if (char === 'tile0' || char === 'tile1') {
if (isTile0Selected) {
useEl.setAttribute('stroke', 'white');
useEl.setAttribute('stroke-width', '2');
} else {
useEl.setAttribute('stroke', 'orange');
useEl.setAttribute('stroke-width', '1');
}
} else if (char === 'K' || char === 'k') {
item.style.opacity = ((char === 'K' && currentTurn === 'w') || (char === 'k' && currentTurn === 'b')) ? '1' : '0.36';
if ((char === 'K' && currentTurn === 'w') || (char === 'k' && currentTurn === 'b')) {
useEl.setAttribute('stroke', 'white');
useEl.setAttribute('stroke-width', '2');
} else {
useEl.setAttribute('stroke', 'none');
}
}
});
}
function handleEditBoardClick(x, y, clickedId) {
if (!isTile0Selected) {
if (!selectedPalettePiece) {
if (clickedId && clickedId[0] !== 'K' && clickedId[0] !== 'k') {
removeEditPiece(clickedId);
saveEditState();
updateEditUI();
setOutputText("Removed piece.", true);
}
} else {
if (clickedId) {
if (clickedId[0] !== 'K' && clickedId[0] !== 'k' && isValidEditPlacement(selectedPalettePiece, x, y)) {
removeEditPiece(clickedId);
placeEditPiece(selectedPalettePiece, x, y);
saveEditState();
updateEditUI();
setOutputText("Replaced piece.", true);
}
} else {
if (isValidEditPlacement(selectedPalettePiece, x, y)) {
placeEditPiece(selectedPalettePiece, x, y);
saveEditState();
updateEditUI();
setOutputText("Placed piece.", true);
}
}
}
} else {
if (editPickedPieceId) {
if (clickedId === editPickedPieceId) {
setPickup(editPickedPieceId, false);
editPickedPieceId = null;
setOutputText("Edit Mode: Select a piece to place or move.", false);
} else if (clickedId) {
setPickup(editPickedPieceId, false);
editPickedPieceId = clickedId;
setPickup(editPickedPieceId, true);
setOutputText("Selected: " + editPickedPieceId, false);
} else {
if (isValidEditPlacement(editPickedPieceId[0], x, y)) {
const pos = piecePos.get(editPickedPieceId);
tileMap.delete(`${pos.x},${pos.y}`);
tileMap.set(`${x},${y}`, editPickedPieceId);
piecePos.set(editPickedPieceId, { x, y });
const pieceEl = document.getElementById(editPickedPieceId);
const vis = getVisualCoords(x, y);
pieceEl.setAttribute('transform', `translate(${vis.x*48+24}, ${vis.y*48+24})`);
setPickup(editPickedPieceId, false);
editPickedPieceId = null;
saveEditState();
updateEditUI();
setOutputText("Moved piece.", true);
}
}
} else {
if (clickedId) {
editPickedPieceId = clickedId;
setPickup(editPickedPieceId, true);
setOutputText("Selected: " + editPickedPieceId, false);
} else {
if (selectedPalettePiece && isValidEditPlacement(selectedPalettePiece, x, y)) {
placeEditPiece(selectedPalettePiece, x, y);
saveEditState();
updateEditUI();
setOutputText("Placed piece.", true);
}
}
}
}
}
Ensure the mini board elements respect the coordinates translation of getVisualCoords().
JavaScript
function updateMiniBoard() {
const miniBoard = document.querySelector('.ejceespbmini');
if (!miniBoard) return;
const targetFen = editMiniBoardState === 0 ? '4k4/9/9/9/9/9/9/9/9/4K4' : INITIAL_FEN.split(' ')[0];
const pieces = miniBoard.querySelectorAll('use');
pieces.forEach(el => {
const href = el.getAttribute('href') || el.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
if (/^#(tile([2-9]|1[0-5]))$/.test(href)) el.remove();
});
const etboardMini = miniBoard.querySelector('.etboard') || miniBoard.querySelector('g:last-child');
targetFen.split('/').forEach((row, y) => {
let x = 0;
for (let i = 0; i < row.length; i++) {
const char = row[i];
if (/[0-9]/.test(char)) x += parseInt(char, 10);
else {
const useEl = document.createElementNS('http://www.w3.org/2000/svg', 'use');
useEl.setAttribute('href', `#${pieceToHrefMap[char]}`);
const vis = getVisualCoords(x, y); // Hook up rotation/flip sync here
useEl.setAttribute('transform', `translate(${vis.x * 48 + 24}, ${vis.y * 48 + 24})`);
etboardMini.appendChild(useEl);
x++;
}
}
});
}
Note: Look for document.getElementById('tool-rotate').addEventListener and tool-flip and add the following line just before their saveStateToUndo();:
JavaScript
if (isEditMode) updateMiniBoard();
Added proper pawn constraints based on standard positioning arrays, and robust validation upon completing board setup.
JavaScript
function isValidEditPlacement(char, x, y) {
if (char === 'K') return x >= 3 && x <= 5 && y >= 7 && y <= 9;
if (char === 'k') return x >= 3 && x <= 5 && y >= 0 && y <= 2;
if (char === 'A') return x >= 3 && x <= 5 && y >= 7 && y <= 9 && Math.abs(x - 4) === Math.abs(y - 8);
if (char === 'a') return x >= 3 && x <= 5 && y >= 0 && y <= 2 && Math.abs(x - 4) === Math.abs(y - 1);
if (char === 'B') return y >= 5 && y <= 9 && y % 2 !== 0 && ((y === 5 || y === 9) ? (x === 2 || x === 6) : (x === 0 || x === 4 || x === 8));
if (char === 'b') return y >= 0 && y <= 4 && y % 2 === 0 && ((y === 0 || y === 4) ? (x === 2 || x === 6) : (x === 0 || x === 4 || x === 8));
if (char === 'P') {
if (y > 6) return false;
if ((y === 5 || y === 6) && x % 2 !== 0) return false;
}
if (char === 'p') {
if (y < 3) return false;
if ((y === 3 || y === 4) && x % 2 !== 0) return false;
}
return true;
}
function exitEditModeCancel() {
isEditMode = false;
document.querySelector('.ejceesstep').style.display = 'flex';
loadFEN(preEditFEN);
renderRecordUI();
renderNoteUI();
updateToolHighlights();
restoreOutputText();
}
function exitEditModeConfirm() {
// Check Validation Rules
let rk = null, bk = null;
for (const [pos, id] of tileMap.entries()) {
if (id[0] === 'K') rk = pos;
if (id[0] === 'k') bk = pos;
}
if (!rk || !bk) {
alert("Both Kings must be present.");
setOutputText("Validation failed: Missing King.", true);
return;
}
if (checkKingsFacing(tileMap)) {
alert("Invalid state: Kings are facing each other.");
setOutputText("Validation failed: Kings facing.", true);
return;
}
const isRedTurn = currentTurn === 'w';
const oppKingPos = isRedTurn ? bk : rk;
const [okX, okY] = oppKingPos.split(',').map(Number);
if (isSquareAttacked(okX, okY, !isRedTurn, tileMap)) {
alert("Invalid state: Active player can directly capture the opponent's King.");
setOutputText("Validation failed: Opponent King in check.", true);
return;
}
let hasValidMove = false;
for (const [pos, id] of tileMap.entries()) {
const pieceIsRed = id[0] === id[0].toUpperCase();
if (pieceIsRed === isRedTurn) {
const [sx, sy] = pos.split(',').map(Number);
for (let tx = 0; tx <= 8; tx++) {
for (let ty = 0; ty <= 9; ty++) {
if (sx === tx && sy === ty) continue;
const targetId = tileMap.get(`${tx},${ty}`);
if (targetId && isSameTeam(id, targetId)) continue;
if (isValidMove(id, sx, sy, tx, ty)) {
hasValidMove = true;
break;
}
}
if (hasValidMove) break;
}
}
if (hasValidMove) break;
}
if (!hasValidMove) {
alert("Invalid state: Active player is stalemated (no valid moves).");
setOutputText("Validation failed: Stalemated.", true);
return;
}
isEditMode = false;
document.querySelector('.ejceesstep').style.display = 'flex';
halfMoveClock = 0;
fullMoveNumber = 1;
const finalFEN = boardToFEN();
historyFEN = {
fen: finalFEN,
move: null,
lastMove: null,
c: "",
v: []
};
currentBranch = [];
currentStepIndex = 0;
stepSlider.max = 0;
stepSlider.value = 0;
saveStateToUndo();
renderRecordUI();
renderNoteUI();
updateToolHighlights();
restoreOutputText();
}